use crate::agent::Agent;
use crate::modifymsg::ModifyMsg;
use crate::rpc_server::{self, RpcServerimpl};
use git2::{Cred, DiffOptions, FetchOptions, Oid, RemoteCallbacks, Repository};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc::UnboundedSender;
use tonic::transport::Server;

pub struct MyServer {
    /// git仓库在server节点中的路径
    repo_path: String,
    /// git仓库实例
    repo: Arc<Mutex<Repository>>,
    /// 维护agents，key为agent的rpc_server地址，value为Agent实例
    agents: Arc<tokio::sync::Mutex<Box<HashMap<String, Agent>>>>,
    /// rpc服务地址
    rpc_server_addr: String,
}

/// new&run
impl MyServer {
    /// new方法，用于创建一个Server实例，传入url和path，将url指向的仓库克隆到path
    pub fn new(url: String, path: String, rpc_server_addr: String) -> Self {
        MyServer {
            repo_path: path.clone(),
            repo: Arc::new(Mutex::new(Self::clone_to_path(&url, &path).unwrap())),
            agents: Arc::new(tokio::sync::Mutex::new(Box::new(HashMap::new()))),
            rpc_server_addr,
        }
    }

    /// run方法，启动文件监控和Server服务
    pub async fn run(&self) -> anyhow::Result<()> {
        // 异步管道，用于通知agent
        let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<ModifyMsg>();
        // 启动文件监控线程
        let monitor_handler = self.start_gitrepo_monitor(tx).await;
        // ModifyMsg的消费者
        let modify_msg_consumer_handler = self.start_consumer(rx).await;

        // 启动rpc服务器，为agent提供服务
        let rpc_handler = self.start_rpc_server().await;

        // 等待异步任务完成
        tokio::try_join!(
            monitor_handler,
            modify_msg_consumer_handler,
            rpc_handler,
        )?;

        Ok(())
    }
}

/// git相关
impl MyServer {
    const LOCAL_BRANCH: &'static str = "refs/heads/master";
    const REMOTE_BRANCH: &'static str = "refs/remotes/origin/master";
    const HEARTBEAT_INTERVAL: tokio::time::Duration = tokio::time::Duration::from_secs(1);

    /// git仓库管理主循环：维护仓库，检查远程仓库是否有更新，向管道中发送更新消息
    async fn start_gitrepo_monitor(
        &self,
        tx: UnboundedSender<ModifyMsg>,
    ) -> tokio::task::JoinHandle<()> {
        // 监控远程仓库，检查是否有更新，若有更新，广播通知agent
        let repo = self.repo.clone();
        //异步管道，如果有更新，通知agent
        tokio::task::spawn(async move {
            loop {
                // 检查并更新仓库
                // todo 需要错误处理，有可能出现网络错误
                if let Ok(Some(modify_msg)) = Self::check_for_updates(&repo.lock().unwrap()) {
                    println!("Repository updated successfully.");
                    // 广播通知agent
                    modify_msg.iter().for_each(|msg| {
                        tx.send(msg.clone()).unwrap();
                    });
                } else {
                    // println!("No updates found.");
                }
                // 等待一秒后继续检查
                tokio::time::sleep(Self::HEARTBEAT_INTERVAL).await;
            }
        })
    }

    /// init 克隆仓库到本地
    fn clone_to_path(url: &str, path: &str) -> Result<Repository, git2::Error> {
        //如果path存在，先删除
        if std::path::Path::new(path).exists() {
            std::fs::remove_dir_all(path).unwrap();
        }
        let mut callbacks = RemoteCallbacks::new();
        // 设置认证回调
        callbacks.credentials(|_url, username_from_url, _allowed_types| {
            Cred::ssh_key(
                username_from_url.unwrap(),
                None,
                std::path::Path::new(&format!("{}/.ssh/id_rsa", std::env::var("HOME").unwrap())),
                None,
            )
        });

        let mut fetch_options = FetchOptions::new();
        // 这个写法为什么不行？expected RemoteCallbacks<'_>, found &mut RemoteCallbacks<'_>
        // fetch_options.remote_callbacks(RemoteCallbacks::new().credentials(
        //     |_url, username_from_url, _allowed_types| {
        //         Cred::ssh_key(
        //             username_from_url.unwrap(),
        //             None,
        //             Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())),
        //             None,
        //         )
        //     },
        // ));
        fetch_options.remote_callbacks(callbacks);

        println!("Cloning repository from {} to {}", url, path);
        git2::build::RepoBuilder::new()
            .fetch_options(fetch_options)
            .clone(url, std::path::Path::new(&path))
    }

    /// 检查并拉取远程更新
    fn check_for_updates(repo: &Repository) -> Result<Option<Vec<ModifyMsg>>, git2::Error> {
        let mut remote = repo.find_remote("origin")?;
        let mut callbacks = RemoteCallbacks::new();
        // 认证回调，处理 Git 认证
        callbacks.credentials(|_url, username_from_url, _allowed_types| {
            Cred::ssh_key(
                username_from_url.unwrap(),
                None,
                std::path::Path::new(&format!("{}/.ssh/id_rsa", std::env::var("HOME").unwrap())),
                None,
            )
        });

        let mut fetch_options = FetchOptions::new();
        fetch_options.remote_callbacks(callbacks);

        // 拉取远程的 master 分支
        remote.fetch(
            &["refs/heads/master:refs/remotes/origin/master"],
            Some(&mut fetch_options),
            None,
        )?;

        // 获取远程和本地的 master 分支
        let local_oid = repo.refname_to_id(Self::LOCAL_BRANCH)?;
        let remote_oid = repo.refname_to_id(Self::REMOTE_BRANCH)?;

        // 如果 oid 不同，说明有更新
        if local_oid != remote_oid {
            println!("Remote changes detected, pulling updates...");
            let diff_files_path = Self::get_diff_files_path(repo, local_oid, remote_oid)?;
            Self::fast_forward(repo, local_oid, remote_oid)?;
            Ok(Some(diff_files_path))
        } else {
            Ok(None)
        }
    }

    /// 快进合并，保证本地分支和远程保持一致，不产生分叉
    fn fast_forward(
        repo: &Repository,
        _local_oid: git2::Oid,
        remote_oid: git2::Oid,
    ) -> Result<(), git2::Error> {
        // 找到本地分支引用
        let mut reference = repo.find_reference(Self::LOCAL_BRANCH)?;

        // 创建日志信息，描述快进操作
        let message = format!("Fast-forward: {} to {}", Self::LOCAL_BRANCH, remote_oid);
        // 将本地分支指向远程的最新提交
        reference.set_target(remote_oid, &message)?;

        // 更新 HEAD 到新的提交，并强制更新工作区
        repo.set_head(Self::LOCAL_BRANCH)?;
        repo.checkout_head(Some(
            git2::build::CheckoutBuilder::default()
                .allow_conflicts(true)
                .force(), // 强制更新工作目录
        ))?;

        println!(
            "Successfully fast-forwarded {} to {}",
            Self::LOCAL_BRANCH,
            Self::REMOTE_BRANCH
        );
        Ok(())
    }

    /// 获取两个提交之间的差异文件
    #[allow(unused_variables)]
    fn get_diff_files_path(
        repo: &Repository,
        local_oid: Oid,
        remote_oid: Oid,
    ) -> Result<Vec<ModifyMsg>, git2::Error> {
        let mut diff_files_path = vec![];
        let local_commit = repo.find_commit(local_oid)?;
        let remote_commit = repo.find_commit(remote_oid)?;

        // 获取两个提交的树对象
        let local_tree = local_commit.tree()?;
        let remote_tree = remote_commit.tree()?;

        // 设置差异选项
        let mut diff_options = DiffOptions::new();

        // 生成差异比较
        let diff = repo.diff_tree_to_tree(
            Some(&local_tree),
            Some(&remote_tree),
            Some(&mut diff_options),
        )?;

        // 遍历diff，处理文件的变动情况
        diff.print(git2::DiffFormat::NameStatus, |delta, _hunk, _line| {
            let old_file_path = delta.old_file().path().unwrap();
            let new_file_path = delta.new_file().path().unwrap();

            // 本地仓库中的文件路径，server中的绝对路径
            let old_file_path_server = PathBuf::from(repo.workdir().unwrap()).join(old_file_path);
            let new_file_path_server = PathBuf::from(repo.workdir().unwrap()).join(new_file_path);

            // 文件在agent节点中的绝对路径
            let old_file_path_agent = PathBuf::from("/").join(old_file_path);
            let new_file_path_agent = PathBuf::from("/").join(new_file_path);

            if old_file_path_agent != new_file_path_agent {
                println!(
                    "!!! old_file_path_agent:{:?} != new_file_path_agent:{:?}",
                    old_file_path_agent, new_file_path_agent
                );
                todo!("old_file_path != new_file_path");
            }
            diff_files_path.push(ModifyMsg::new(
                old_file_path_server,
                new_file_path_agent,
                delta.status(),
            ));
            true
        })?;

        Ok(diff_files_path)
    }
}

/// agent相关
impl MyServer {
    /// 启动rpc服务器，为agent提供服务
    async fn start_rpc_server(&self) -> tokio::task::JoinHandle<()> {
        let addr = self.rpc_server_addr.parse().unwrap();
        let rpc_server = RpcServerimpl::new(self.repo_path.clone(), self.agents.clone());
        tokio::task::spawn(async move {
            #[cfg(debug_assertions)]
            println!("Rpc server listening on {}", addr);
            Server::builder()
                .add_service(
                    rpc_server::rpc_server::config_monitor_s_server::ConfigMonitorSServer::new(
                        rpc_server,
                    ),
                )
                .serve(addr)
                .await
                .unwrap();
        })
    }

    /// channel消费者，处理ModifyMsg，广播通知agent
    /// 如果agent没有成功处理ModifyMsg，将agent从agents中移除
    async fn start_consumer(
        &self,
        mut rx: tokio::sync::mpsc::UnboundedReceiver<ModifyMsg>,
    ) -> tokio::task::JoinHandle<()> {
        let agents = self.agents.clone();
        tokio::task::spawn(async move {
            while let Some(msg) = rx.recv().await {
                // Received file change: ModifyMsg { file_path_server: "/tmp/test/h.json", file_path_agent: "/h.json", delta: Added }
                // Received file change: ModifyMsg { file_path_server: "/tmp/test/h.json", file_path_agent: "/h.json", delta: Deleted }
                // Received file change: ModifyMsg { file_path_server: "/tmp/test/h.json", file_path_agent: "/h.json", delta: Modified }
                // 在我的测试中，delta类型只有Added, Deleted, Modified
                // 其他delta类型？Renamed，Copied都能转换成Deleted+Added
                if msg.delta != git2::Delta::Added
                    && msg.delta != git2::Delta::Deleted
                    && msg.delta != git2::Delta::Modified
                {
                    todo!("!!! delta:{:?} is not supported", msg.delta);
                }
                #[cfg(debug_assertions)]
                println!("Received file change: {:?}", msg);

                let mut agents_guard = agents.lock().await;
                let agent_addrs_to_remove: Vec<_> =
                    futures::future::join_all(agents_guard.iter().map(|(agent_addr, agent)| {
                        let (msg, agent) = (msg.clone(), agent.clone());
                        let agent_addr = agent_addr.clone();
                        tokio::spawn(async move {
                            if agent.sync_config_file(msg).await.is_err() {
                                Some(agent_addr)
                            } else {
                                None
                            }
                        })
                    }))
                    .await
                    .into_iter()
                    .filter_map(|res| res.ok().flatten())
                    .collect();

                for agent_addr in agent_addrs_to_remove {
                    agents_guard.remove(&agent_addr);
                    #[cfg(debug_assertions)]
                    println!("Agent {} is dead, removing...", agent_addr);
                }
            }
        })
    }
}
